package graphNew;

import gui.AgentChooser;
import io.IoHandler;

import java.awt.Dimension;
import java.awt.Paint;
import java.security.acl.LastOwnerException;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import javax.swing.JFrame;

import main.Main;
import main.ProgramOptions;
import main.Translating;
import main.ProgramOptions.CutChoise;
import main.ProgramOptions.UnSplitable;
import nl.uu.cs.treewidth.algorithm.TreewidthDP;

import org.apache.commons.collections15.Transformer;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DefaultWeightedEdge;


import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

import components.Agent;
import components.Goal;
import components.Operator;
import components.Variable;

public class DinicBasedAlgo {
	//all the agents
	private Set<Agent> agents;
	//the target of the algorithem
	private double targetCut,targetAgents;
	//the first graph
	private DinicMainGraph first;
	
	private DefaultDirectedWeightedGraph<Agent, AgentEdge> lastAgentGraph;
	
	/**
	 * CONSTRUCTOR
	 * @param mg the first maingraph from the user input
	 * @param goalsgoals of the graph
	 * @throws Exception from DinicMainGraph constructor
	 */
	public DinicBasedAlgo (MainGraph mg,Vector<Goal> goals) throws Exception{
		agents=new HashSet<Agent>();
		targetCut=targetAgents=-1;
		this.first=new DinicMainGraph(goals,mg);
		drawDinicGraph();
		agents.add(new Agent(this.first,0));
	}
	
	
	//make output visualization (to the screen) of the graph with sourse and sink nodes
	public void drawDinicGraph(){
		Graph jungGraph = Translating.dinicNetworkToJungGraph(this.first);
		
		// The Layout<V, E> is parameterized by the vertex and edge types
		Layout<Operator, DefaultWeightedEdge> layout = new FRLayout<Operator, DefaultWeightedEdge>(jungGraph);
		layout.setSize(new Dimension(500,500)); // sets the initial size of the space
		
		// The BasicVisualizationServer<V,E> is parameterized by the edge types
		VisualizationViewer<Operator,DefaultWeightedEdge> vv =
		new VisualizationViewer<Operator,DefaultWeightedEdge>(layout);
		vv.setPreferredSize(new Dimension(600,600)); //Sets the viewing area size
		
		//transformer design pattern for painting sink / source differently
		Transformer<Operator,Paint> vertexPaint = new Transformer<Operator,Paint>() {
			public Paint transform(Operator op) {
				return op.getPaintingColor();
			}
		};
		
		//Vertices and edges view editing
		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaint);
		vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<Operator>());
		//vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<DefaultWeightedEdge>());
		vv.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
		
		//iterative mouse for ZOOM IN/OUT
		DefaultModalGraphMouse<Operator,DefaultWeightedEdge> gm = 
			new DefaultModalGraphMouse<Operator,DefaultWeightedEdge>();
		gm.setMode(ModalGraphMouse.Mode.TRANSFORMING);
		vv.setGraphMouse(gm);
		
		//graphic output / visualization
		JFrame frame = new JFrame("Main Graph");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(vv);
		frame.pack();
		frame.setVisible(true);	
	}
	
	
	/**
	 * sets target of the algorithem
	 * @param cutTarget
	 * @param agentTarget
	 */
	public void setTarget(int cutTarget,int agentTarget){
		targetCut=cutTarget;
		targetAgents=agentTarget;
	}
	
	/**
	 * the algorithem
	 * @throws Exception no target has been setter
	 */
	public void runAlgo() throws Exception{
		
		if (targetCut==-1 && targetAgents==-1)
			throw new Exception ("user didnt set target");
		
		Agent candidate=getCandidate();
		while ((targetAgents==-1)?(candidate!=null):(agents.size()<targetAgents && candidate!=null)){
			split(candidate);
			candidate=getCandidate();
		}
		
		for (Agent i:agents){
			i.getGraph().getMaingraph().removeVertex(i.getGraph().getSource());
			i.getGraph().getMaingraph().removeVertex(i.getGraph().getSink());
			i.removeOperators(i.getGraph().getSource(),i.getGraph().getSink());
			i.putAgentId();
		}
	}

	/**
	 * splits the candidate agent into 2 agents
	 * @param candidate
	 * @throws Exception from dinicFlow or minCut
	 */
	private void split(Agent candidate) throws Exception {
		DefaultDirectedWeightedGraph<Operator, FlowingEdge> right=new DefaultDirectedWeightedGraph<Operator, FlowingEdge>(FlowingEdge.class);
		DefaultDirectedWeightedGraph<Operator, FlowingEdge> left=new DefaultDirectedWeightedGraph<Operator, FlowingEdge>(FlowingEdge.class);
		DefaultDirectedWeightedGraph<Operator, FlowingEdge> cut=new DefaultDirectedWeightedGraph<Operator, FlowingEdge>(FlowingEdge.class);
		Operator sourceR, sourceL, sinkR,sinkL;
		
		sourceL=candidate.getGraph().getSource();
		sinkR=candidate.getGraph().getSink();
		sinkL=new Operator("sink");
		sourceR=new Operator("source");
		
		
		candidate.getGraph().dinicFlow();
		int cutSum=candidate.getGraph().minCut(left,right,cut);
		
		if (left.vertexSet().size()==1 || right.vertexSet().size()==1){
			if (ProgramOptions.optionUnsplitable==UnSplitable.LEAVE){
				candidate.unSplitable();
				return;
			}else
				if (candidate.getEdgeRank()<ProgramOptions.keepTrying){
					candidate.raiseEdgesFromSource();
					candidate.getGraph().reset();
					return;
				}else if (left.vertexSet().size()==1 || right.vertexSet().size()==1){
					candidate.unSplitable();
					System.out.println(candidate.getAgentId()+" UNSPLITTABLE!!!!");
					return;
				}
		}
		
		left.addVertex(sinkL);
		right.addVertex(sourceR);
		
		for (FlowingEdge i:cut.edgeSet()){
			cut.getEdgeSource(i).makePublic();
			cut.getEdgeTarget(i).makePublic();
			FlowingEdge e=left.addEdge(cut.getEdgeSource(i), sinkL);
			if (e!=null)
				e.setCapacity(1);
			e=right.addEdge(sourceR,cut.getEdgeTarget(i));
			if (e!=null)
				e.setCapacity(1);
		}
		
		DinicMainGraph gRight=new DinicMainGraph(right, sourceR, sinkR);
		DinicMainGraph gLeft= new DinicMainGraph(left, sourceL, sinkL);
		
		System.out.println("-----------------------");
		System.out.println(candidate.setNew(gRight,cutSum));
		Agent temp=new Agent(gLeft,cutSum);
		System.out.println(temp);
		System.out.println("-----------------------");
		agents.add(temp);
		
		
	}

	/**
	 * gets the right candidate
	 * @return
	 */
	private Agent getCandidate() {
		if (ProgramOptions.cutChoise==CutChoise.LARGE){
			double minRank=-1000;
			Agent ret=null;
			for (Agent i:agents)
				if (i.isSplitable())
					if ((targetCut==-1)?(i.getRank()> minRank):(i.getRank()>minRank&& i.getRank()<targetCut)){
						ret=i;
						minRank=i.getRank();
					}			
			return ret;
		}else{
			double minRank=Double.MAX_VALUE;
			Agent ret=null;
			for (Agent i:agents)
				if (i.isSplitable())
					if ((targetCut==-1)?(i.getRank()< minRank):(i.getRank()<minRank&& i.getRank()<targetCut)){
						ret=i;
						minRank=i.getRank();
					}	
			return ret;
		}
	}
/**
 * GETTER
 * @return
 */
	public Set<Agent> agentSet() {
		return agents;
	}
/**
 * Creates agent graph and returns it
 * @param first the first agentgraph
 * @return
 * @throws Exception
 */
	public DefaultDirectedWeightedGraph<Agent, AgentEdge> getAgentGraph (MainGraph first) throws Exception{
		DefaultDirectedWeightedGraph<Agent, AgentEdge> answer = new DefaultDirectedWeightedGraph<Agent, AgentEdge>(AgentEdge.class);
		for (Agent i:agents)
			answer.addVertex(i);
		for (Agent i:agents)
			for (Agent j:agents)
				if (i!=j){
					int weightIJ=0;
					AgentEdge e;
					DefaultWeightedEdge weightedEdge;
					for (Operator opI:i.getGraph().getMaingraph().vertexSet())
						for (Operator opJ:j.getGraph().getMaingraph().vertexSet())
							if ((weightedEdge = first.getMainGraph().getEdge(opI, opJ)) != null){
								weightIJ+=first.getMainGraph().getEdgeWeight(weightedEdge);
							}	
					e = new AgentEdge(null);
					e.setNumOfOriginalEdges(weightIJ);
					if (weightIJ>0){
						answer.addEdge(i, j, e);
						answer.setEdgeWeight(e, weightIJ);	//it is not important
						e.setNumOfOriginalEdges(weightIJ);
					}
				}
		this.lastAgentGraph=answer;
		return answer;
	}
	
/**
 * GETTER	
 * @return
 */
	public DefaultDirectedWeightedGraph<Agent, AgentEdge> getAgentGraph (){
	    return lastAgentGraph;
	}
	
/**
 * THE MAIN ALGORITHM OF ALL THE PROCESS
 * including input , output,and GUI	
 * @throws NumberFormatException
 * @throws Exception
 */
	public static void mainAlgo() throws NumberFormatException, Exception{
		Vector<Variable> vars=new Vector<Variable>();
		Vector<Goal> goals= new Vector<Goal>();
		Vector<Operator> operators= new Vector<Operator>();
		IoHandler.ParseFile(ProgramOptions.inputFile, vars, goals, operators);
		MainGraph mg= new MainGraph(operators);
		DinicBasedAlgo dba= new DinicBasedAlgo(mg, goals);
		dba.setTarget(ProgramOptions.rankLimit==0?-1:ProgramOptions.rankLimit, ProgramOptions.numberOfAgents);
		dba.runAlgo();
		IoHandler.outputToFile(ProgramOptions.outputFile, ProgramOptions.inputFile, vars, goals, operators);
		DefaultDirectedWeightedGraph<Agent,AgentEdge> ag= dba.getAgentGraph(mg);
		Main.paintAgentGraph(ag);
		
		
		TreewidthDP tw=new TreewidthDP();
		tw.setInput(Translating.ourGraphToListGraph(ag));
		tw.run();
		AgentChooser.main(tw.getTreewidth(),dba.agentSet());
		
		
		
		
		
	}


	

}
